package net.loogn.stardust.common.utils;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * Created by Administrator on 2017/4/25.
 */
public class ObjectGenerator {


    private static int DefaultCollectionSize = 1;
    private final SimpleTypeObjectGenerator SimpleObjectGenerator = new SimpleTypeObjectGenerator();

    public Object GenerateObject(Class type) {
        return GenerateObject(type, null, new HashMap<>());
    }

    public Object GenerateObject(Class type, Type genericType) {
        return GenerateObject(type, genericType, new HashMap<>());
    }

    public Object GenerateObject(ParameterizedType type) {
        Class cls = (Class) type.getRawType();
        return GenerateObject(cls, type, new HashMap<>());
    }

    private Object GenerateObject(Class type, Type genericType, Map<Class, Object> createdObjectReferences) {

        try {
            if (SimpleObjectGenerator.CanGenerateObject(type)) {
                return SimpleObjectGenerator.GenerateObject(type);
            }
            if (type.isArray()) {
                return GenerateArray(type, DefaultCollectionSize, createdObjectReferences);
            }
            if (List.class.isAssignableFrom(type)) {
                return GenerateList(genericType, DefaultCollectionSize, createdObjectReferences);
            }
            if (Map.class.isAssignableFrom(type)) {
                return GenerateMap(genericType, DefaultCollectionSize, createdObjectReferences);
            }
            if (Set.class.isAssignableFrom(type)) {
                return GenerateSet(genericType, DefaultCollectionSize, createdObjectReferences);
            }

            return GenerateComplexObject(type, createdObjectReferences);
        } catch (Exception exp) {
            exp.printStackTrace();
            return null;
        }
    }


    private static Object GenerateArray(Class arrayType, int size, Map<Class, Object> createdObjectReferences) {
        Class type = arrayType.getComponentType();
        Object result = Array.newInstance(type, size);

        boolean areAllElementsNull = true;
        ObjectGenerator objectGenerator = new ObjectGenerator();
        for (int i = 0; i < size; i++) {
            Object element = objectGenerator.GenerateObject(type, null, createdObjectReferences);
            Array.set(result, i, element);
            areAllElementsNull &= element == null;
        }
        if (areAllElementsNull) {
            return null;
        }
        return result;
    }

    private static Object GenerateList(Type genericType, int size, Map<Class, Object> createdObjectReferences) throws IllegalAccessException, InstantiationException {

        List list = ArrayList.class.newInstance();
        ObjectGenerator objectGenerator = new ObjectGenerator();
        Class type = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0];
        for (int i = 0; i < size; i++) {
            Object element = objectGenerator.GenerateObject(type, null, createdObjectReferences);
            list.add(element);
        }
        return list;
    }

    private static Object GenerateMap(Type genericType, int size, Map<Class, Object> createdObjectReferences) throws IllegalAccessException, InstantiationException {

        Map map = HashMap.class.newInstance();
        ObjectGenerator objectGenerator = new ObjectGenerator();
        Class keyType = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0];
        Class valueType = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[1];
        for (int i = 0; i < size; i++) {
            Object key = objectGenerator.GenerateObject(keyType, null, createdObjectReferences);
            Object value = objectGenerator.GenerateObject(valueType, null, createdObjectReferences);
            map.put(key, value);
        }
        return map;
    }

    private static Object GenerateSet(Type genericType, int size, Map<Class, Object> createdObjectReferences) throws IllegalAccessException, InstantiationException {

        Set set = HashSet.class.newInstance();
        ObjectGenerator objectGenerator = new ObjectGenerator();
        Class elementType = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0];
        for (int i = 0; i < size; i++) {
            Object element = objectGenerator.GenerateObject(elementType, null, createdObjectReferences);
            set.add(element);
        }
        return set;
    }

    private static Object GenerateComplexObject(Class type, Map<Class, Object> createdObjectReferences) throws IllegalAccessException, InstantiationException {

        Object result = null;
//        if (createdObjectReferences.containsKey(type)) {
//            // The object has been created already, just return it. This will handle the circular reference case.
//            return createdObjectReferences.get(type);
//        }
        result = type.newInstance();
        createdObjectReferences.put(type, result);
        SetPublicFields(type, result, createdObjectReferences);
        return result;
    }

    private static void SetPublicFields(Class type, Object obj, Map<Class, Object> createdObjectReferences) {
        ObjectGenerator objectGenerator = new ObjectGenerator();
        for (Field field : type.getDeclaredFields()) {

            Object fieldValue = objectGenerator.GenerateObject(field.getType(), field.getGenericType(), createdObjectReferences);
            field.setAccessible(true);
            try {
                field.set(obj, fieldValue);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }


    private interface ISimpleGenerator {
        Object Init(long index);
    }

    public static class SimpleTypeObjectGenerator {

        private long _index;
        private static final Map<Class, ISimpleGenerator> DefaultGenerators = InitializeGenerators();

        private static Map<Class, ISimpleGenerator> InitializeGenerators() {
            Map<Class, ISimpleGenerator> map = new HashMap<>();
            map.put(boolean.class, index -> true);
            map.put(Boolean.class, index -> true);
            map.put(byte.class, index -> (byte) 64);
            map.put(Byte.class, index -> (byte) 64);
            map.put(char.class, index -> (char) 65);
            map.put(Character.class, index -> (char) 65);
            map.put(Date.class, index -> new Date());
            map.put(BigDecimal.class, index -> new BigDecimal(index));
            map.put(BigInteger.class, index -> BigInteger.valueOf(index));
            map.put(int.class, index -> (int) (index % Integer.MAX_VALUE));
            map.put(Integer.class, index -> (int) (index % Integer.MAX_VALUE));
            map.put(short.class, index -> (short) (index % Short.MAX_VALUE));
            map.put(Short.class, index -> (short) (index % Short.MAX_VALUE));
            map.put(long.class, index -> index);
            map.put(Long.class, index -> index);
            map.put(float.class, index -> (float) index);
            map.put(Float.class, index -> (float) index);
            map.put(double.class, index -> (double) index);
            map.put(Double.class, index -> (double) index);
            map.put(String.class, index -> "str" + index);
            map.put(Object.class, index -> new Object());
            map.put(UUID.class, index -> UUID.randomUUID());
            map.put(URI.class, index -> {
                try {
                    return new URI("http://www.abc.com");
                } catch (URISyntaxException e) {
                    return null;
                }
            });

            return map;
        }

        public static boolean CanGenerateObject(Class cls) {
            return DefaultGenerators.containsKey(cls);
        }

        public Object GenerateObject(Class cls) {
            return DefaultGenerators.get(cls).Init(++_index);
        }
    }

}
